home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / HTTP / Client.php next >
Encoding:
PHP Script  |  2007-12-20  |  18.0 KB  |  548 lines

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at                              |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Author: Alexey Borzov <avb@php.net>                                  |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Client.php,v 1.7 2006/06/03 08:54:10 avb Exp $
  20.  
  21. /*
  22.  * Do this define in your script if you wish HTTP_Client to follow browser 
  23.  * quirks rather than HTTP specification (RFC2616). This means:
  24.  *   - do a GET request after redirect with code 301, rather than use the
  25.  *     same method as before redirect.
  26.  */
  27. // define('HTTP_CLIENT_QUIRK_MODE', true);
  28.  
  29. require_once 'HTTP/Request.php';
  30. require_once 'HTTP/Client/CookieManager.php';
  31.  
  32. /**
  33.  * A simple HTTP client class.
  34.  * 
  35.  * The class wraps around HTTP_Request providing a higher-level
  36.  * API for performing multiple HTTP requests
  37.  * 
  38.  * @package HTTP_Client
  39.  * @author Alexey Borzov <avb@php.net>
  40.  * @version $Revision: 1.7 $
  41.  */
  42. class HTTP_Client
  43. {
  44.    /**
  45.     * An HTTP_Client_CookieManager instance
  46.     * @var object
  47.     */
  48.     var $_cookieManager;
  49.  
  50.    /**
  51.     * Received HTTP responses
  52.     * @var array
  53.     */
  54.     var $_responses;
  55.  
  56.    /**
  57.     * Default headers to send on every request
  58.     * @var array
  59.     */
  60.     var $_defaultHeaders = array();
  61.  
  62.    /**
  63.     * Default parameters for HTTP_Request's constructor
  64.     * @var array
  65.     */
  66.     var $_defaultRequestParams = array();
  67.  
  68.    /**
  69.     * How many redirects were done
  70.     * @var integer
  71.     */
  72.     var $_redirectCount = 0;
  73.  
  74.    /**
  75.     * Maximum allowed redirects
  76.     * @var integer
  77.     */
  78.     var $_maxRedirects = 5;
  79.  
  80.    /**
  81.     * Listeners attached to the client  
  82.     * @var array
  83.     */
  84.     var $_listeners = array();
  85.  
  86.    /**
  87.     * Whether the listener should be propagated to Request objects
  88.     * @var array
  89.     */
  90.     var $_propagate = array();
  91.  
  92.    /**
  93.     * Whether to keep all the responses or just the most recent one
  94.     * @var boolean
  95.     */
  96.     var $_isHistoryEnabled = true;
  97.  
  98.    /**
  99.     * Constructor
  100.     * 
  101.     * @access   public
  102.     * @param    array   Parameters to pass to HTTP_Request's constructor
  103.     * @param    array   Default headers to send on every request
  104.     * @param    object  HTTP_Client_CookieManager   Cookie manager object to use
  105.     */
  106.     function HTTP_Client($defaultRequestParams = null, $defaultHeaders = null, $cookieManager = null)
  107.     {
  108.         if (!empty($cookieManager) && is_a($cookieManager, 'HTTP_Client_CookieManager')) {
  109.             $this->_cookieManager = $cookieManager;
  110.         } else {
  111.             $this->_cookieManager =& new HTTP_Client_CookieManager();
  112.         }
  113.         if (isset($defaultHeaders)) {
  114.             $this->setDefaultHeader($defaultHeaders);
  115.         }
  116.         if (isset($defaultRequestParams)) {
  117.             $this->setRequestParameter($defaultRequestParams);
  118.         }
  119.     }
  120.  
  121.  
  122.    /**
  123.     * Sets the maximum redirects that will be processed.
  124.     * 
  125.     * Setting this to 0 disables redirect processing. If not 0 and the 
  126.     * number of redirects in a request is bigger than this number, then an
  127.     * error will be raised.
  128.     * 
  129.     * @access   public
  130.     * @param    int     Max number of redirects to process
  131.     */
  132.     function setMaxRedirects($value)
  133.     {
  134.         $this->_maxRedirects = $value;
  135.     }
  136.  
  137.  
  138.    /**
  139.     * Sets whether to keep all the responses or just the most recent one
  140.     *
  141.     * @access public
  142.     * @param  bool      Whether to enable history
  143.     */
  144.     function enableHistory($enable)
  145.     {
  146.         $this->_isHistoryEnabled = (bool)$enable;
  147.     }
  148.  
  149.    /**
  150.     * Creates a HTTP_Request objects, applying all the necessary defaults
  151.     *
  152.     * @param    string   URL
  153.     * @param    string   Method, constants are defined in HTTP_Request
  154.     * @param    array    Extra headers to send
  155.     * @access   private
  156.     * @return   object   HTTP_Request object with all defaults applied
  157.     */
  158.     function &_createRequest($url, $method = HTTP_REQUEST_METHOD_GET, $headers = array())
  159.     {
  160.         $req =& new HTTP_Request($url, $this->_defaultRequestParams);
  161.         $req->setMethod($method);
  162.         foreach ($this->_defaultHeaders as $name => $value) {
  163.             $req->addHeader($name, $value);
  164.         }
  165.         foreach ($headers as $name => $value) {
  166.             $req->addHeader($name, $value);
  167.         }
  168.         $this->_cookieManager->passCookies($req);
  169.         foreach ($this->_propagate as $id => $propagate) {
  170.             if ($propagate) {
  171.                 $req->attach($this->_listeners[$id]);
  172.             }
  173.         }
  174.         return $req;
  175.     }
  176.     
  177.  
  178.    /**
  179.     * Sends a 'HEAD' HTTP request
  180.     *
  181.     * @param    string  URL
  182.     * @param    array   Extra headers to send
  183.     * @access   public
  184.     * @return   integer HTTP response code
  185.     * @throws   PEAR_Error
  186.     */
  187.     function head($url, $headers = array())
  188.     {
  189.         $request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_HEAD, $headers);
  190.         return $this->_performRequest($request);
  191.     }
  192.    
  193.  
  194.    /**
  195.     * Sends a 'GET' HTTP request
  196.     * 
  197.     * @param    string  URL
  198.     * @param    mixed   additional data to send
  199.     * @param    boolean Whether the data is already urlencoded
  200.     * @param    array   Extra headers to send
  201.     * @access   public
  202.     * @return   integer HTTP response code
  203.     * @throws   PEAR_Error
  204.     */
  205.     function get($url, $data = null, $preEncoded = false, $headers = array())
  206.     {
  207.         $request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_GET, $headers);
  208.         if (is_array($data)) {
  209.             foreach ($data as $name => $value) {
  210.                 $request->addQueryString($name, $value, $preEncoded);
  211.             }
  212.         } elseif (isset($data)) {
  213.             $request->addRawQueryString($data, $preEncoded);
  214.         }
  215.         return $this->_performRequest($request);
  216.     }
  217.  
  218.  
  219.    /**
  220.     * Sends a 'POST' HTTP request
  221.     *
  222.     * @param    string  URL
  223.     * @param    mixed   Data to send
  224.     * @param    boolean Whether the data is already urlencoded
  225.     * @param    array   Files to upload. Elements of the array should have the form:
  226.     *                   array(name, filename(s)[, content type]), see HTTP_Request::addFile()
  227.     * @param    array   Extra headers to send
  228.     * @access   public
  229.     * @return   integer HTTP response code
  230.     * @throws   PEAR_Error
  231.     */
  232.     function post($url, $data, $preEncoded = false, $files = array(), $headers = array())
  233.     {
  234.         $request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_POST, $headers);
  235.         if (is_array($data)) {
  236.             foreach ($data as $name => $value) {
  237.                 $request->addPostData($name, $value, $preEncoded);
  238.             }
  239.         } else {
  240.             $request->addRawPostData($data, $preEncoded);
  241.         }
  242.         foreach ($files as $fileData) {
  243.             $res = call_user_func_array(array(&$request, 'addFile'), $fileData);
  244.             if (PEAR::isError($res)) {
  245.                 return $res;
  246.             }
  247.         }
  248.         return $this->_performRequest($request);
  249.     }
  250.  
  251.  
  252.    /**
  253.     * Sets default header(s) for HTTP requests
  254.     *
  255.     * @param    mixed   header name or array ('header name' => 'header value')
  256.     * @param    string  header value if $name is not an array
  257.     * @access   public
  258.     */
  259.     function setDefaultHeader($name, $value = null)
  260.     {
  261.         if (is_array($name)) {
  262.             $this->_defaultHeaders = array_merge($this->_defaultHeaders, $name);
  263.         } else {
  264.             $this->_defaultHeaders[$name] = $value;
  265.         }
  266.     }
  267.  
  268.  
  269.    /**
  270.     * Sets parameter(s) for HTTP requests
  271.     *
  272.     * @param    mixed   parameter name or array ('parameter name' => 'parameter value')
  273.     * @param    string  parameter value if $name is not an array
  274.     * @access   public
  275.     */
  276.     function setRequestParameter($name, $value = null)
  277.     {
  278.         if (is_array($name)) {
  279.             $this->_defaultRequestParams = array_merge($this->_defaultRequestParams, $name);
  280.         } else {
  281.             $this->_defaultRequestParams[$name] = $value;
  282.         }
  283.     }
  284.       
  285.  
  286.    /**
  287.     * Performs a request, processes redirects
  288.     *
  289.     * @param    object  HTTP_Request object
  290.     * @access   private
  291.     * @return   integer HTTP response code
  292.     * @throws   PEAR_Error
  293.     */
  294.     function _performRequest(&$request)
  295.     {
  296.         // If this is not a redirect, notify the listeners of new request
  297.         if (0 == $this->_redirectCount && !empty($request->_url)) {
  298.             $this->_notify('request', $request->_url->getUrl());
  299.         }
  300.         if (PEAR::isError($err = $request->sendRequest())) {
  301.             $this->_redirectCount = 0;
  302.             return $err;
  303.         }
  304.         $this->_pushResponse($request);
  305.  
  306.         $code = $request->getResponseCode();
  307.         if ($this->_maxRedirects > 0) {
  308.             if (in_array($code, array(300, 301, 302, 303, 307))) {
  309.                 if ('' == ($location = $request->getResponseHeader('Location'))) {
  310.                     $this->_redirectCount = 0;
  311.                     return PEAR::raiseError("No 'Location' field on redirect");
  312.                 }
  313.                 // Bug #5759: do not try to follow non-HTTP redirects
  314.                 if (null === ($redirectUrl = $this->_redirectUrl($request->_url, $location))) {
  315.                     $this->_redirectCount = 0;
  316.                     return $code;
  317.                 }
  318.             // Redirect via <meta http-equiv="Refresh"> tag, see request #5734
  319.             } elseif (200 <= $code && $code < 300) {
  320.                 $redirectUrl = $this->_getMetaRedirect($request);
  321.             }
  322.         }
  323.         if (!empty($redirectUrl)) {
  324.             if (++$this->_redirectCount > $this->_maxRedirects) {
  325.                 $this->_redirectCount = 0;
  326.                 return PEAR::raiseError('Too many redirects');
  327.             }
  328.             // Notify of redirection
  329.             $this->_notify('httpRedirect', $redirectUrl);
  330.             // we access the private properties directly, as there are no accessors for them
  331.             switch ($request->_method) {
  332.                 case HTTP_REQUEST_METHOD_POST: 
  333.                     if (302 == $code || 303 == $code || (301 == $code && defined('HTTP_CLIENT_QUIRK_MODE'))) {
  334.                         return $this->get($redirectUrl);
  335.                     } else {
  336.                         $postFiles = array();
  337.                         foreach ($request->_postFiles as $name => $data) {
  338.                             $postFiles[] = array($name, $data['name'], $data['type']);
  339.                         }
  340.                         return $this->post($redirectUrl, $request->_postData, true, $postFiles);
  341.                     }
  342.                 case HTTP_REQUEST_METHOD_HEAD:
  343.                     return (303 == $code? $this->get($redirectUrl): $this->head($redirectUrl));
  344.                 case HTTP_REQUEST_METHOD_GET: 
  345.                 default:
  346.                     return $this->get($redirectUrl);
  347.             } // switch
  348.  
  349.         } else {
  350.             $this->_redirectCount = 0;
  351.             if (400 >= $code) {
  352.                 $this->_notify('httpSuccess');
  353.                 $this->setDefaultHeader('Referer', $request->_url->getUrl());
  354.                 // some result processing should go here
  355.             } else {
  356.                 $this->_notify('httpError');
  357.             }
  358.         }
  359.         return $code;
  360.     }
  361.  
  362.  
  363.    /**
  364.     * Returns the most recent HTTP response
  365.     * 
  366.     * @access public
  367.     * @return array
  368.     */
  369.     function ¤tResponse()
  370.     {
  371.         return $this->_responses[count($this->_responses) - 1];
  372.     }
  373.  
  374.  
  375.    /**
  376.     * Saves the server's response to responses list
  377.     *
  378.     * @param    object  HTTP_Request object, with request already sent
  379.     * @access   private
  380.     */
  381.     function _pushResponse(&$request)
  382.     {
  383.         $this->_cookieManager->updateCookies($request);
  384.         $idx   = $this->_isHistoryEnabled? count($this->_responses): 0;
  385.         $this->_responses[$idx] = array(
  386.             'code'    => $request->getResponseCode(),
  387.             'headers' => $request->getResponseHeader(),
  388.             'body'    => $request->getResponseBody()
  389.         );
  390.     }
  391.  
  392.  
  393.    /**
  394.     * Clears object's internal properties
  395.     *
  396.     * @access public
  397.     */
  398.     function reset()
  399.     {
  400.         $this->_cookieManager->reset();
  401.         $this->_responses            = array();
  402.         $this->_defaultHeaders       = array();
  403.         $this->_defaultRequestParams = array();
  404.     }
  405.  
  406.  
  407.    /**
  408.     * Adds a Listener to the list of listeners that are notified of
  409.     * the object's events
  410.     * 
  411.     * @param    object   HTTP_Request_Listener instance to attach
  412.     * @param    boolean  Whether the listener should be attached to the 
  413.     *                    created HTTP_Request objects
  414.     * @return   boolean  whether the listener was successfully attached
  415.     * @access   public
  416.     */
  417.     function attach(&$listener, $propagate = false)
  418.     {
  419.         if (!is_a($listener, 'HTTP_Request_Listener')) {
  420.             return false;
  421.         }
  422.         $this->_listeners[$listener->getId()] =& $listener;
  423.         $this->_propagate[$listener->getId()] =  $propagate;
  424.         return true;
  425.     }
  426.  
  427.  
  428.    /**
  429.     * Removes a Listener from the list of listeners 
  430.     * 
  431.     * @param    object   HTTP_Request_Listener instance to detach
  432.     * @return   boolean  whether the listener was successfully detached
  433.     * @access   public
  434.     */
  435.     function detach(&$listener)
  436.     {
  437.         if (!is_a($listener, 'HTTP_Request_Listener') || 
  438.             !isset($this->_listeners[$listener->getId()])) {
  439.             return false;
  440.         }
  441.         unset($this->_listeners[$listener->getId()], $this->_propagate[$listener->getId()]);
  442.         return true;
  443.     }
  444.  
  445.  
  446.    /**
  447.     * Notifies all registered listeners of an event.
  448.     * 
  449.     * Currently available events are:
  450.     * 'request': sent on HTTP request that is not a redirect
  451.     * 'httpSuccess': sent when we receive a successfull 2xx response
  452.     * 'httpRedirect': sent when we receive a redirection response
  453.     * 'httpError': sent on 4xx, 5xx response
  454.     * 
  455.     * @param    string  Event name
  456.     * @param    mixed   Additional data
  457.     * @access   private
  458.     */
  459.     function _notify($event, $data = null)
  460.     {
  461.         foreach (array_keys($this->_listeners) as $id) {
  462.             $this->_listeners[$id]->update($this, $event, $data);
  463.         }
  464.     }
  465.  
  466.  
  467.    /**
  468.     * Calculates the absolute URL of a redirect
  469.     *  
  470.     * @param    object  Net_Url object containing the request URL
  471.     * @param    string  Value of the 'Location' response header
  472.     * @return   string|null  Absolute URL we are being redirected to, null in case of non-HTTP URL 
  473.     * @access   private
  474.     */
  475.     function _redirectUrl($url, $location)
  476.     {
  477.         // If it begins with a scheme (as defined in RFC 2396) then it is absolute URI 
  478.         if (preg_match('/^([a-zA-Z][a-zA-Z0-9+.-]*):/', $location, $matches)) {
  479.             // Bug #5759: we shouldn't try to follow non-HTTP redirects
  480.             if ('http' == strtolower($matches[1]) || 'https' == strtolower($matches[1])) {
  481.                 return $location;
  482.             } else {
  483.                 return null;
  484.             }
  485.         } else {
  486.             if ('/' == $location{0}) {
  487.                 $url->path = Net_URL::resolvePath($location);
  488.             } elseif('/' == substr($url->path, -1)) {
  489.                 $url->path = Net_URL::resolvePath($url->path . $location);
  490.             } else {
  491.                 $dirname = (DIRECTORY_SEPARATOR == dirname($url->path)? '/': dirname($url->path));
  492.                 $url->path = Net_URL::resolvePath($dirname . '/' . $location);
  493.             }
  494.             $url->querystring = array();
  495.             $url->anchor      = '';
  496.             return $url->getUrl();
  497.         }
  498.     }
  499.  
  500.  
  501.    /**
  502.     * Returns the cookie manager object (e.g. for storing it somewhere)
  503.     *
  504.     * @return object HTTP_Client_CookieManager
  505.     * @access public
  506.     */
  507.     function getCookieManager()
  508.     {
  509.         return $this->_cookieManager;
  510.     }
  511.  
  512.  
  513.    /**
  514.     * Tries to extract a redirect URL from <meta http-equiv=Refresh> tag (request #5734)
  515.     *
  516.     * @param    object HTTP_Request     A request object containing the response
  517.     * @return   string|null             Absolute URI we are being redirected to, null if no redirect / invalid redirect
  518.     * @access   private
  519.     */
  520.     function _getMetaRedirect(&$request)
  521.     {
  522.         // Non-HTML response or empty response body
  523.         if ('text/html' != substr($request->getResponseHeader('content-type'), 0, 9) ||
  524.             '' == ($body = $request->getResponseBody())) {
  525.             return null;
  526.         }
  527.         // No <meta http-equiv=Refresh> tag
  528.         if (!preg_match('!<meta\\s+([^>]*http-equiv\\s*=\\s*("Refresh"|\'Refresh\'|Refresh)[^>]*)>!is', $body, $matches)) {
  529.             return null;
  530.         }
  531.         // Just a refresh, no redirect
  532.         if (!preg_match('!content\\s*=\\s*("[^"]+"|\'[^\']+\'|\\S+)!is', $matches[1], $urlMatches)) {
  533.             return null;
  534.         }
  535.         $parts = explode(';', ('\'' == substr($urlMatches[1], 0, 1) || '"' == substr($urlMatches[1], 0, 1))? 
  536.                                substr($urlMatches[1], 1, -1): $urlMatches[1]);
  537.         if (empty($parts[1]) || !preg_match('/url\\s*=\\s*(\\S+)/is', $parts[1], $urlMatches)) {
  538.              return null;
  539.         }
  540.         // We do finally have an url... Now check that it's:
  541.         // a) HTTP, b) not to the same page
  542.         $previousUrl = $request->_url->getUrl();
  543.         $redirectUrl = $this->_redirectUrl($request->_url, html_entity_decode($urlMatches[1]));
  544.         return (null === $redirectUrl || $redirectUrl == $previousUrl)? null: $redirectUrl; 
  545.     }
  546. }
  547. ?>
  548.